# -*- coding: utf-8 -*-

import binascii
import random
import time

from tablestore import *

import requests
from Crypto import Random
from Crypto.Cipher import AES
from GKModel.BaseModel import BaseModel


class Client(BaseModel):
    OPENID_TYPE_CLIENT = 1
    APP_ID = 'wx734af0f3845f8a8b'
    APP_SECRECT = '28473ea2fe0ed23d4917869251e0be5d'
    OPEN_ID_TABLE_NAME = 'ThirdClient'
    CLIENT_TABLE_NAME = 'Clients'
    SECRET = b'I828u31lg4uvbo1v'

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    @staticmethod
    def __gen_random_name():
        return ''.join(random.sample('abcdefghijklmnopqrstuvwxyz', 5)) + ''.join(random.sample('1234567890', 3))

    def __insert_new_client(self):
        try:
            while True:
                account = Client.__gen_random_name()
                inclusive_start_primary_key = [('account', account), ('uid', INF_MIN)]
                exclusive_end_primary_key = [('account', account), ('uid', INF_MAX)]
                columns_to_get = []

                consumed, next_start_primary_key, row_list, next_token = self.ots_client.get_range(
                    table_name=Client.CLIENT_TABLE_NAME,
                    direction=Direction.FORWARD,
                    inclusive_start_primary_key=inclusive_start_primary_key,
                    exclusive_end_primary_key=exclusive_end_primary_key,
                    columns_to_get=columns_to_get,
                    limit=1,
                    column_filter=None,
                    max_version=1
                )
                if row_list is None or len(row_list) == 0:
                    create_time = int(time.time())
                    primary_key = [('account', account), ('uid', PK_AUTO_INCR)]
                    attribute_column = [('create_time', create_time), ('update_time', create_time)]
                    row = Row(primary_key, attribute_column)
                    consumed, return_row = self.ots_client.put_row(Client.CLIENT_TABLE_NAME, row)
                    return account

        except OTSServiceError as e:
            raise ValueError(1000009, e.get_error_message())
        except OTSClientError as e:
            raise ValueError(1000010, e.get_error_message())

    def __query_client_by_account(self, account):
        try:
            inclusive_start_primarhy_key = [('account', account), ('uid', INF_MIN)]
            exclusive_end_primary_key = [('account', account), ('uid', INF_MAX)]
            columns_to_get = []

            consumed, next_start_primary_key, row_list, next_token = self.ots_client.get_range(
                table_name=Client.CLIENT_TABLE_NAME,
                direction=Direction.FORWARD,
                inclusive_start_primary_key=inclusive_start_primarhy_key,
                exclusive_end_primary_key=exclusive_end_primary_key,
                columns_to_get=columns_to_get,
                limit=1,
                column_filter=None,
                max_version=1
            )
            return dict(row_list[0].primary_key)
        except OTSServiceError as e:
            raise ValueError(1000012, e.get_error_message())
        except OTSClientError as e:
            raise ValueError(1000013, e.get_error_message())

    def __insert_client_open_id(self, account, uid, openid):
        primary_key = [('openid', openid)]
        attribute_columns = [('account', account), ('uid', uid), ('type', Client.OPENID_TYPE_CLIENT)]
        row = Row(primary_key, attribute_columns)
        condition = Condition(RowExistenceExpectation.EXPECT_NOT_EXIST)

        try:
            consumed, return_row = self.ots_client.put_row(Client.OPEN_ID_TABLE_NAME,
                                                           row,
                                                           condition)
        except OTSServiceError as e:
            raise ValueError(1000014, e.get_error_message())
        except OTSClientError as e:
            raise ValueError(1000016, e.get_error_message())

    def __query_client_by_open_id(self, openid):
        primary_key = [('openid', openid)]
        columns_to_get = ['account', 'uid']
        cond = SingleColumnCondition('type', Client.OPENID_TYPE_CLIENT, ComparatorType.EQUAL)

        try:
            consumed, return_row, next_token = self.ots_client.get_row(Client.OPEN_ID_TABLE_NAME,
                                                                       primary_key,
                                                                       columns_to_get,
                                                                       cond,
                                                                       1)
            if return_row is None:
                account = self.__insert_new_client()
                result = self.__query_client_by_account(account)
                self.__insert_client_open_id(account, result['uid'], openid)
            else:
                result = {}
                for att in return_row.attribute_columns:
                    result[att[0]] = att[1]

            return result
        except OTSClientError as e:
            raise ValueError(1000007, e.get_error_message())
        except OTSServiceError as e:
            raise ValueError(1000008, e.get_error_message())

    @staticmethod
    def __make_token(account, uid):
        expired = int(time.time())
        plain = ','.join([account, str(uid), str(expired)]).encode()
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(Client.SECRET, AES.MODE_CFB, iv)
        client_token = iv + cipher.encrypt(plain)
        return binascii.b2a_base64(client_token, newline=False).decode()

    @staticmethod
    def __update_token(ots_client, client_token, account, uid):
        primary_key = [('account', account), ('uid', uid)]
        update_of_attribute_columns = {
            'PUT': [('token', client_token)]
        }
        row = Row(primary_key, update_of_attribute_columns)
        condition = Condition(RowExistenceExpectation.IGNORE)
        consumed, return_row = ots_client.update_row(Client.CLIENT_TABLE_NAME, row, condition)

    @staticmethod
    def __parse_token(client_token):
        if client_token is None or str(client_token) == 0:
            raise ValueError(1000017, 'Invalid Token')

        e = binascii.a2b_base64(client_token)
        iv = e[:16]
        cipher = AES.new(Client.SECRET, AES.MODE_CFB, iv)
        d = cipher.decrypt(e[16:]).decode()
        result = str.split(d, ',')
        if len(result) != 3:
            raise ValueError(1000017, 'Invalid Token')

        result[1] = int(result[1])
        return result

    def __query_client(self, account, uid):
        primary_key = [('account', account), ('uid', uid)]
        columns_to_get = []
        try:
            consumed, return_row, next_token = self.ots_client.get_row(
                Client.CLIENT_TABLE_NAME,
                primary_key,
                columns_to_get,
                None,
                1
            )
            data = {
                'account': account,
            }
            for item in return_row.attribute_columns:
                data[item[0]] = item[1]

            return data
        except OTSClientError as e:
            raise ValueError(1000011, e.get_error_message())
        except OTSServiceError as e:
            raise ValueError(1000012, e.get_error_message())

    def wx_login(self, code):
        url = 'https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type' \
              '=authorization_code '.format(Client.APP_ID, Client.APP_SECRECT, code)
        r = requests.get(url)
        result = r.json()
        if 'errcode' in result:
            raise ValueError(1000006, result.get('errmsg', ''))

        openid = result['openid']
        result = self.__query_client_by_open_id(openid)
        client_token = self.__make_token(result['account'], result['uid'])
        Client.__update_token(self.ots_client, client_token, result['account'], result['uid'])
        data = self.__query_client(result['account'], result['uid'])
        data['openid'] = openid

        return data, client_token

    def update(self, nick_name=None, avatar_url=None):
        if self.client_token is None:
            raise ValueError(1000019, 'need token')

        account, uid, expired = Client.__parse_token(self.client_token)
        primary_key = [('account', account), ('uid', uid)]
        update_of_attribute_columns_put = []
        if nick_name is not None and len(nick_name) != 0:
            update_of_attribute_columns_put.append(('nick_name', nick_name))
        if avatar_url is not None and len(avatar_url) != 0:
            update_of_attribute_columns_put.append(('avatar_url', avatar_url))
        update_of_attribute_columns = {
            'PUT': update_of_attribute_columns_put
        }
        row = Row(primary_key, update_of_attribute_columns)

        condition = Condition(RowExistenceExpectation.IGNORE)

        try:
            consumed, return_row = self.ots_client.update_row(Client.CLIENT_TABLE_NAME, row, condition)
            new_token = self.__make_token(account, uid)
            Client.__update_token(self.ots_client, new_token, account, uid)
            data = self.__query_client(account, uid)
            return data, new_token
        except OTSClientError as e:
            raise ValueError(1000019, e.get_error_message())
        except OTSServiceError as e:
            raise ValueError(1000019, e.get_error_message())

    def update_token(self):
        if self.client_token is None:
            return ''
        account, uid, expired = Client.__parse_token(self.client_token)
        new_token = self.__make_token(account, uid)
        Client.__update_token(self.ots_client, new_token, account, uid)
        return new_token

    def query_account(self):
        if self.client_token is None:
            return None

        account, uid, expired = Client.__parse_token(self.client_token)
        primary_key = [('account', account), ('uid', uid)]
        try:
            consumed, return_row, next_token = self.ots_client.get_row(
                table_name=Client.CLIENT_TABLE_NAME,
                primary_key=primary_key
            )

            if return_row is None:
                return None

            return return_row.primary_key

        except OTSServiceError as e:
            raise ValueError(1000023, e.get_error_message())
        except OTSClientError as e:
            raise ValueError(1000024, e.get_error_message())


def new(**kwargs) -> Client:
    return Client(**kwargs)
